/**
 * Copyright (c) Martijn Grootens (m.e.grootens@student.utwente.nl), 
 * University of Twente.
 * Created july 2012.
 *
 * This file is part of dbcleaner.
 * 
 * dbcleaner is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * dbcleaner is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with dbcleaner.  If not, see <http://www.gnu.org/licenses/>.
 */

package dbcleaner.core.parse;
import java.util.*;
import dbcleaner.core.dbentries.*;
import dbcleaner.core.bibfields.*;

/**
 * Utils for creating Entry instances from a String that should contain
 * the plain text that forms a BibTeX reference.
 */
public class EntryFactory {
    
    // The EntryFactory needs a list of all present BibTeX reference class
    // types, in order to compare the supplied reference to.
    // There currently are 12 classes; if more need to be added, don't forget
    // to change the initial static counter value of the Entry class.
    private static final Entry[] class_types = {
    new Article(),
    new Book(),
    new InCollection(),
    new Proceedings(),
    new PhdThesis(),
    new Misc(),
    new Unpublished(),
    new InBook(),
    new InProceedings(),
    new TechReport(),
    new MastersThesis(),
    new Electronic(),
    };
    
    // Notes in CiteULike are separated by NOTE_SEP. The new separator
    // is NOTE_SEP_REPLACE.
    private static final String NOTE_SEP = "---=note-separator=---";
    private static final String NOTE_SEP_REPLACE = " | ";
    
    /**
     * Create an Entry of the given class and add its contents.
     */
    public static Entry get_entry(String entry_class, String contents) {
        
        // Fetch empty instance of right Entry subclass
        Entry e = get_empty_entry(entry_class);
        
        // Scanner for contents; i.e. to distinguish the BibTeX fields and
        // their contents.
        Scanner scan = new Scanner(contents);
        
        // The line that is scanned, and possible something to prepend.
        String ln = "";
        String prepend = "";
        
        // While there still are lines to read
        while (scan.hasNextLine()) {
            
            // Fetch a new line and prepend.
            ln = prepend + scan.nextLine();
            
            // If ln does not represent a valid BibField yet, more date
            // must be read, i.e. read a new line and prepend the previous one.
            if (!is_BibField(ln)) {
                prepend = ln.replaceAll(NOTE_SEP,NOTE_SEP_REPLACE) + "\n";
            } else {
                // If ln represents a BibField, created it and append in 
                // to the Entry.
                prepend = "";
                e.add_BibField(to_BibField(ln));
            }
            
        }
        
        // The Entry is finished; all fields were read.
        return e;
        
    }
    
    
    
    /**
     * Figure out the class name of the reference and return a new instance of 
     * this type.
     */
    private static Entry get_empty_entry (String entry_class) {
        
        // Simply compare the class name of the reference to the cubclasses
        // of Entry.
        for (Entry e : class_types) {
            
            if (e.get_class_name().equals(entry_class)) {
                try {
                    // Create a new instance of the right class.
                    // And return.
                    return e.getClass().newInstance();
                }
                catch (Exception ee) {
                    System.out.println(ee);
                } 
            }
            
        }
        
        // If we get here, the class is unknown.
        System.out.println("Unknown entry class: " + entry_class);
        System.exit(1);
        return null;
        
    }
    
    
    
    /**
     * Check if the String is a complete BibField. Try to deny truth by finding
     * - String length of zero chars
     * - missing "=" symbol
     * - curly brackets are unbalanced
     * Else must be true.
     */
    private static boolean is_BibField (String s) {
        
        if (s.length()==0)  // length of sring should be larger than 0
            return false;
        else if (s.indexOf('=')==-1)    // string should contain '='
            return false;
        
        // Count number of left and right brackets; add lefts, substract rights.
        int bracket = 0;
        for (int ii=0; ii < s.length(); ++ii) {
            char ch = s.charAt(ii);
            if (ch=='{')
                ++bracket;
            else if (ch=='}')
                --bracket;
        }
        
        // number of left and right brackets should be equal
        if (bracket!=0)
            return false;
        
        return true;
        
    }
    
    /**
     * Create a BibField of a string that represents a BibField.
     * (Could surely be done in a more fancy way, using regex and stuff.)
     */
    private static BibField to_BibField (String s) {
        
        // Index of the "=" symbol.
        int eq_ind = s.indexOf('=');
        
        // Extract field name.
        String name = s.substring(0,eq_ind);
        
        // Cut off space.
        if (name.lastIndexOf(' ') == name.length()-1)
            name = name.substring(0,name.length()-1);
        
        // Extract field contents.
        String content = s.substring(eq_ind+1);
        
        // Cut off space.
        if (content.indexOf(' ') == 0)
            content = content.substring(1);
        
        // Remove final comma
        if (content.lastIndexOf(',') == content.length()-1)
            content = content.substring(0,content.length()-1);
        
        // Start peeling of the outer brackets. Ensure that e.g.
        // "{{foo} and {bar}}" is correctly interpreted as
        // "{foo} and {bar}" and not to "foo} and {bar", which is wrong,
        // but still is balanced.
        boolean cutting_brackets = true;
        
        while (cutting_brackets) {
            
            if (
                content.indexOf('{') == 0 
                && 
                content.lastIndexOf('}') == content.length()-1
                &&
                content.indexOf('{',1) < content.indexOf('}',1)
                )
                content = content.substring(1,content.length()-1);
            else
                cutting_brackets = false;
        }
        
        return new BibField(name, content);
        
    }
    
    
    
}